/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_SOCKETSTREAM_H
#define GENERIK_SOCKETSTREAM_H

/** \file socketstream.h
Implements the socketstream classes.
These use the socketstreambuf class, which is derived from the streambuf
class, to implement the buffering of data to and from a generik socket.
*/

#include "streambuf.h"
#include "socket.h"

#include <istream>
#include <ostream>

#include "tracing.h"

#define PRINTNULL(...)

//#define PRINTFDEBUG
#ifdef PRINTFDEBUG
#define PRINTFDEBUG_ fprintf
#else
#define PRINTFDEBUG_ PRINTNULL
#endif

namespace generik
{
	// forward decleration with default template argument
	template <typename _CharT, typename _Traits = std::char_traits<_CharT> >
	struct basic_isocketstream;

	// forward decleration with default template argument
	template <typename _CharT, typename _Traits = std::char_traits<_CharT> >
	struct basic_osocketstream;

	// forward decleration with default template argument
	template <typename _CharT, typename _Traits = std::char_traits<_CharT> >
	struct basic_socketstream;



	/** generik socket stream buffer
	This derives from the generik streambuf class to implement a buffer
	for the data from and to a generik socket.
	*/
	template <typename _CharT, typename _Traits>
	struct basic_socketstreambuf
	: public basic_streambuf<_CharT, _Traits>
	{
		typedef _CharT		char_type;
		typedef _Traits		traits_type;
		typedef typename traits_type::int_type	int_type;
		
		typedef basic_streambuf<char_type, traits_type>	__streambuf_type;

		/// The socket connection used by this streambuf
		socket::connection* __connection;

		/// constructor
		/** This constructs a socket connection using the provided host and port info,
		and uses it to send and receive data.
		The connection is explicitly closed when this instance is destroyed.
		*/
		basic_socketstreambuf(int port_in, const std::string& addr_in = socket::LOCALHOST)
		: __connection(new socket::connection(port_in, addr_in))
		{}
		
		/** This uses the provided socket connection to send and receive data to and from.
		The connection is explicitly closed when this instance is destroyed, if it has been set.
		Use attach() to set the connection after construction, if required.
		*/
		basic_socketstreambuf(socket::connection* c_in = 0)
		: __connection(c_in)
		{}

		/** destructor
		This automatically destroys the connection, if it still exists
		*/
		~basic_socketstreambuf()
		{
			REM_GENERIK_TRACER;
			if (__connection)
				delete __connection;
			;
		}

		/// Attaches a pre-existing connection to this streambuf, returns the previous connection
		socket::connection* attach(socket::connection* c_in)
		{
			socket::connection* current = __connection;
			__connection = c_in;
			return current;
		}
		/// Detaches a previously attached connection from this streambuf, and returns a pointer to it
		socket::connection* detach()
		{
			return attach(0);
		}
		/// Opens a connection, and proceeds to use it
		void open(int port, const std::string& addr_in = socket::LOCALHOST)
		{
			if (__connection)
			{
				// a connection has already been set. Do we destroy it? return an error? throw something?
				delete __connection;
			}
			__connection = new socket::connection;
			__connection->connect(port, addr_in);
		}
		/// Closes the currently open connection
		void close()
		{
			__connection->close();
		}
		
		bool is_open() const
		{
			return __connection->is_open();
		}
		
		// called when the getbuffer is empty (ie, next == 0 or next == end)
		// this means we have to recv() from the connection, writing the maximum
		// number of bytes into the buffer, and setting g_end to point to the last
		// character read, using greset(end)
		virtual int_type underflow()
		{
			PRINTFDEBUG_(stderr, "socketstream::underflow()\n");
			int count = __connection->recv(__streambuf_type::__gbuffer, __streambuf_type::__buffer_size());
			PRINTFDEBUG_(stderr, "socketstream::underflow: count is [%i]\n", count);
			if (count > 0)
			{
				std::string message(__streambuf_type::__gbuffer, count);
				PRINTFDEBUG_(stderr, "socketstream::underflow: received data is [%s]\n", message.c_str());
				__streambuf_type::g_reset(count);
				int_type chr = __streambuf_type::sgetc();
				PRINTFDEBUG_(stderr, "socketstream::underflow: ok, returning [%c]\n", chr);
				return chr;
			}
			else
			if (count < 0)
			{
				// an error occurred.
				// we have two options, simply return the count (and have the user deal with it)
				// or throw an exception. This should be some kind of configurable behaviour
				PRINTFDEBUG_(stderr, "socketstream::underflow: error: %s\n", strerror(errno));
				//
				// this seems to work under linux
				//std::string msg = "error during underflow: ";
				//throw std::runtime_error(msg + strerror(errno));
				// but not under cygwin; here, only the following works?
				return _Traits::eof();
			}
			else
			{
				//__streambuf_type::g_reset(&__streambuf_type::__gbuffer[0]);
				//__streambuf_type::g_reset();
				PRINTFDEBUG_(stderr, "socketstream::underflow: no characters read - returning end of file\n");
				return _Traits::eof();
			}
		}
		
		// called when the putbuffer is full (ie, next == end)
		// this means we simply send() to the connection, and reset the putbuffer
		virtual int_type overflow(int_type __c = _Traits::eof())
		{
			PRINTFDEBUG_(stderr, "socketstream::overflow: overflow()\n");
			int diff = __streambuf_type::_M_out_cur - __streambuf_type::_M_out_beg;
			PRINTFDEBUG_(stderr, "socketstream::overflow: cur [%p] beg [%p] diff [%i]\n", __streambuf_type::_M_out_cur, __streambuf_type::_M_out_beg, diff);
			std::string message(__streambuf_type::__pbuffer, diff);
			PRINTFDEBUG_(stderr, "socketstream::overflow: sending [%s]", message.c_str());
			//int result = __connection->send(__streambuf_type::__pbuffer, diff);
			int result = sendall();
			PRINTFDEBUG_(stderr, "socketstream::overflow: result is %i\n", result);
			if (result < 0)
			{
				PRINTFDEBUG_(stderr, "socketstream::overflow: error during overflow: %s\n", strerror(errno));
				// an error occurred during sending.
				// how do we handle this? We can return the negative value and have the caller deal with it,
				// or throw an exception. This should be configurable.
				std::string msg = "error during overflow: ";
				throw std::runtime_error(msg + strerror(errno));
				//return _Traits::eof();
			}
			else
			if (result != diff)
			{
				// we failed to send the entire send buffer.
				// this is definitely an error, otherwise we would still be stuck in sendall().
				// how do we handle this? by throwing an exception? returning EOF?
				//__streambuf_type::p_reset_offset();
				PRINTFDEBUG_(stderr, "socketstream::overflow: failed to send entire buffer\n");
				__streambuf_type::p_reset();
				return _Traits::eof();
			}
			else
			{
				PRINTFDEBUG_(stderr, "socketstream::overflow: ok\n");
				// everything seems ok
				__streambuf_type::p_reset();
				//__streambuf_type::p_reset_offset();
				if (__c != _Traits::eof())
					__streambuf_type::sputc(__c);
			}
			return __c;
		}
		
		/** send all the data in the buffer.
		This method attempts to send all the data in the buffer, and will try again and again until either all data has been sent,
		or an error has occurred.
		It therefore blocks until the entire buffer could be sent.
		*/
		int sendall()
		{
			int target = __streambuf_type::_M_out_cur - __streambuf_type::_M_out_beg;
			int count = target;
			int total = 0;
			if (target > 0)
			{
				// we have something to send
				int result = __connection->send(__streambuf_type::_M_out_beg, count);
				while (true)
				{
					if (result > 0)
					{
						// good, some data has been sent
					}
					else
					if (result < 0)
					{
						// some kind of error occurred
						return -1;
					}
					else
					{
						// could not send all characters
						return 0;
					}
					total += result;
					if (target == result)
					{
						// all the data has been sent, break out of the loop
						break;
					}
					PRINTFDEBUG_(stderr, "socketstream::sendall: not all data could be sent: [%i] out of [%i], retrying\n", result, target);
					__streambuf_type::_M_out_beg += result;
					target = __streambuf_type::_M_out_cur - __streambuf_type::_M_out_beg;
					count = target;
					result = __connection->send(__streambuf_type::_M_out_beg, count);
				}
				//total += result;
				return total;
			}
			return 0; // how to handle the situation when we have nothing to send
		}
		
		/*
		// called when the putbuffer is empty, and a previous character is requested
		// ie. next == begin, during sputbackc(__c) or sungetc(__c)
		virtual int_type pbackfail(int_type __c = _Traits::eof())
		{
			//todo
			if (_pnext != _pbegin)
			{
				--_pnext;
				if (__c != _Traits::eof()) (*_pnext) = __c;
				return *_pnext; //?
			}
			return _Traits::eof();
			return __streambuf_type::overflow(__c);
		}
		*/
		
		/** syncs the stream.
		Forces the overflow() call, to flush the output buffer.
		\note should this call underflow as well? no, because the input data will have nowhere to go right then?
		*/
		int sync()
		{
			overflow();
			return 0;
		}
		
	};



	/// basic generik socket input stream
	template <typename _CharT, typename _Traits>
	struct basic_isocketstream
	: public std::basic_istream<_CharT, _Traits>
	{
		typedef _CharT		char_type;
		typedef _Traits 	traits_type;

		typedef basic_socketstreambuf<char_type, traits_type> __buf_type;
		__buf_type _M_buf;

		basic_isocketstream()
		{
			this->init(&_M_buf);
		}

		basic_isocketstream(socket::connection* c_in)
		: _M_buf(c_in)
		{
			this->init(&_M_buf);
		}

		basic_isocketstream(int port, const std::string& addr = socket::LOCALHOST)
		: _M_buf(port, addr)
		{
			this->init(&_M_buf);
		}

		void sync()
		{
			this->_M_buf.sync();
		}
		
		void open(int port) { _M_buf.open(port); }
		void close() { _M_buf.close(); }
		
		bool is_open() const { return _M_buf.is_open(); }
		
		socket::connection* attach(socket::connection* c_in) { return _M_buf.attach(c_in); }
		socket::connection* detach() { return _M_buf.detach(); }
	};



	/// basic generik socket output stream
	template <typename _CharT, typename _Traits>
	struct basic_osocketstream
	: public std::basic_ostream<_CharT, _Traits>
	{
		typedef _CharT		char_type;
		typedef _Traits 	traits_type;

		typedef basic_socketstreambuf<char_type, traits_type> __buf_type;
		__buf_type	_M_buf;

		basic_osocketstream()
		{
			this->init(&_M_buf);
		}

		basic_osocketstream(socket::connection* c_in)
		: _M_buf(c_in)
		{
			this->init(&_M_buf);
		}

		basic_osocketstream(int port, const std::string& addr = socket::LOCALHOST)
		: _M_buf(port, addr)
		{
			this->init(&_M_buf);
		}
		
		void sync()
		{
			this->_M_buf.sync();
		}

		void open(int port) { _M_buf.open(port); }
		void close() { _M_buf.close(); }
		
		bool is_open() const { return _M_buf.is_open(); }
		
		socket::connection* attach(socket::connection* c_in) { return _M_buf.attach(c_in); }
		socket::connection* detach() { return _M_buf.detach(); }
	};



	/// basic generik socket input-output stream
	template <typename _CharT, typename _Traits>
	struct basic_socketstream
	: public std::basic_iostream<_CharT, _Traits>
	{
		typedef _CharT		char_type;
		typedef _Traits 	traits_type;

		typedef basic_socketstreambuf<char_type, traits_type> __buf_type;
		__buf_type	_M_buf;

		basic_socketstream()
		{
			this->init(&_M_buf);
		}

		basic_socketstream(socket::connection* c_in)
		: _M_buf(c_in)
		{
			this->init(&_M_buf);
		}

		basic_socketstream(int port, const std::string& addr = socket::LOCALHOST)
		: _M_buf(port, addr)
		{
			this->init(&_M_buf);
		}

		void sync()
		{
			this->_M_buf.sync();
		}
		
		void open(int port) { _M_buf.open(port); }
		void close() { _M_buf.close(); }
		
		bool is_open() const { return _M_buf.is_open(); }
		
		socket::connection* attach(socket::connection* c_in) { return _M_buf.attach(c_in); }
		socket::connection* detach() { return _M_buf.detach(); }
	};

	/** default generik socket input/output stream
	Usage:
		generik::socket::connection*  c = new generik::socket::connection;
		c->connect(4756);
		generik::socketstream ss(c);
	or
		generik::socketstream ss;
		ss.open(4756);
	or 
		generik::socketstream ss(port);
	
	c is automatically destroyed unless ss.detach() is called.
	In this case, the socket::connection* is returned. It is then up to the caller to destroy/close
	the connection.
	*/
	typedef basic_socketstream<char> socketstream;

	// default generik socket input only stream
	typedef basic_isocketstream<char> isocketstream;

	// default generik socket output only stream
	typedef basic_osocketstream<char> osocketstream;

}

#endif





